package com.kaikeba.mvc;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 映射器（包含了大量的网址与方法的对应关系）
 */
public class HandlerMapping {
    private static Map<String,MVCMapping> data = new HashMap<>();
    //获取用户传入的URI，然后去data中找相应的URI。最后映射到类中的相应方法
    public static MVCMapping get(String uri){
        return data.get(uri);//这个get方法相当于查找
    }

    //这个load方法就是将DispatcherServlet中获取的配置文件中的内容映射到相应的类，然后通过反射调用类中的方法
    public static void load(InputStream is) {
        //Properties对象用于解析配置文件中的内容（已经转化为这里的字节输入流is了）
        //解析完成后就能获取到其中存储的类的地址
        Properties ppt = new Properties();
        try {
            ppt.load(is);//Properties类中的load方法可以解析properties类型的文件
        } catch (IOException e) {
            e.printStackTrace();
        }
        //解析完成后获取Properties对象中的值（此处是类地址）并保存到集合中。
        Collection<Object> values = ppt.values();
        for (Object cla:values) {
            String className = (String) cla;//将获取到的类地址转换为字符串（之前是Object）
            try {
                //以下就是通过反射获取对应类中的方法了（不清楚去回顾笔记）
                //将配置文件中描述的每一个类加载到内存中（反射基础知识）
                Class c = Class.forName(className);
                //创建这个类的对象
                Object obj = c.getConstructor().newInstance();
                //获取这个类的所有方法（这里是所有公有方法因为是getMethods）
                Method[] methods = c.getMethods();
                for (Method m:methods) {
                    //记得我们自定义两个注解，都是作用于方法的。（注解的value都是请求地址）
                    //所以在获取到类中的所有方法后再获取相应方法上的注解
                    Annotation[] as = m.getAnnotations();
                    if(as != null){
                        //对该方法上的所有注解进行遍历
                        for(Annotation annotation:as){
                            //判断每个注解的类型，然后分别进行相应的操作
                            if(annotation instanceof ResponseBody){
                                //说明此方法，用于返回字符串给客户端
                                //MVCMapping中保存的东西：当前类对象、类中的某个有注解的方法、响应的类型（自定义的枚举类）
                                MVCMapping mapping = new MVCMapping(obj,m,ResponseType.TEXT);
                                //data中保存的东西：键是注解的值（相应的请求地址），值是自定义的MVCMapping对象
                                //put方法有返回值，如果为null则表明新增。
                                Object o = data.put(((ResponseBody) annotation).value(),mapping);
                                //当处理类中的方法上的注解的值重复时报错，因为每个方法的请求地址都要不一样。
                                if(o != null){
                                    //存在了重复的请求地址
                                    throw new RuntimeException("请求地址重复："+((ResponseBody) annotation).value());
                                }
                            }else if(annotation instanceof ResponseView){
                                //说明此方法，用于返回界面（跳转界面）给客户端
                                MVCMapping mapping = new MVCMapping(obj,m,ResponseType.VIEW);
                                Object o =  data.put(((ResponseView) annotation).value(),mapping);
                                if(o != null){
                                    //存在了重复的请求地址
                                    throw new RuntimeException("请求地址重复："+((ResponseView) annotation).value());
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 映射对象，每一个对象封装了一个方法，用于处理请求
     */
    public static class MVCMapping{
        private Object obj;
        private Method method;
        private ResponseType type;//之前定义的用于描述响应（Response）的类型的枚举类对象

        public MVCMapping() {
        }

        public MVCMapping(Object obj, Method method, ResponseType type) {
            this.obj = obj;
            this.method = method;
            this.type = type;
        }

        public Object getObj() {
            return obj;
        }

        public void setObj(Object obj) {
            this.obj = obj;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public ResponseType getType() {
            return type;
        }

        public void setType(ResponseType type) {
            this.type = type;
        }
    }
}
